package com.stox.client.ui.javafx.chartingy.view;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.SplitPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.WindowEvent;

import com.stox.client.core.model.intf.Navigable;
import com.stox.client.ui.javafx.chartingy.event.listener.ChartingMouseEventStrategy;
import com.stox.client.ui.javafx.chartingy.event.listener.PanMouseEventStrategy;
import com.stox.client.ui.javafx.chartingy.event.listener.ZoomEventListener;
import com.stox.client.ui.javafx.chartingy.model.Zoom;
import com.stox.client.ui.javafx.chartingy.model.chart.Chart;
import com.stox.client.ui.javafx.chartingy.model.chart.PrimaryChart;
import com.stox.client.ui.javafx.chartingy.model.enumeration.BarType;
import com.stox.client.ui.javafx.chartingy.model.plot.Plot;
import com.stox.client.ui.javafx.chartingy.model.plot.VolumePlot;
import com.stox.client.ui.javafx.chartingy.widget.ChartingConfigurationPane;
import com.stox.client.ui.javafx.chartingy.widget.Crosshair;
import com.stox.client.ui.javafx.chartingy.widget.menu.ChartingContextMenu;
import com.stox.client.ui.javafx.chartingy.widget.toolbar.ChartingToolBar;
import com.stox.client.ui.javafx.chartingy.widget.toolbar.DrawingToolBar;
import com.stox.client.ui.javafx.core.util.CSS;
import com.stox.client.ui.javafx.core.util.UI;
import com.stox.client.ui.javafx.widget.ConfigurationPopupControl;
import com.stox.client.ui.javafx.widget.RangeSlider;
import com.stox.client.workbench.ui.javafx.stage.StoxStage;
import com.stox.client.workbench.ui.javafx.view.View;
import com.stox.shared.model.Bar;
import com.stox.shared.model.Scrip;
import com.stox.shared.model.enumeration.BarSpan;
import com.stox.shared.model.enumeration.Span;
import com.stox.shared.model.filter.BarCriteria;
import com.stox.shared.model.intf.Callback;
import com.stox.shared.model.intf.HasScrip;

public class ChartingView extends View {

	private final ObjectProperty<Span> spanProperty = new SimpleObjectProperty<>(Span.YEAR_2);
	private final ObjectProperty<BarSpan> barSpanProperty = new SimpleObjectProperty<>(BarSpan.DAILY);
	private final ObjectProperty<BarType> barTypeProperty = new SimpleObjectProperty<>(BarType.HLC);
	private final DoubleProperty barGapProperty = new SimpleDoubleProperty(0.3);
	private final ObjectProperty<Navigable<HasScrip>> navigableProperty = new SimpleObjectProperty<>();
	private final ListProperty<Chart<?, ?>> chartsProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
	private final PanMouseEventStrategy panMouseEventStrategy = new PanMouseEventStrategy();
	private final ObjectProperty<ChartingMouseEventStrategy> mouseEventStrategyProperty = new SimpleObjectProperty<ChartingMouseEventStrategy>(panMouseEventStrategy);
	private final ToggleGroup mouseEventStrategyToggleGroup = new ToggleGroup();

	private double pnfBoxSize = 5;
	private boolean pnfBoxSizeInPercentage = true;
	private int pnfReversalAmount = 1;

	private double kagiReversalSize = 4;
	private boolean kagiReversalSizeInPercentage = true;

	private double renkoReversalSize = 4;
	private boolean renkoReversalSizeInPercentage = true;

	private int lineBreakReversalAmount = 3;

	private boolean maintainRatio = false;
	private final BarCriteria barCriteria = new BarCriteria();

	private final SplitPane splitPane = new SplitPane();
	private final RangeSlider rangeSlider = new RangeSlider(0, 1, 0, 1);
	private final PrimaryChart primaryChart = new PrimaryChart(this);
	private final Plot<Scrip, Bar> volumePlot = new VolumePlot(this);
	private final ChartingToolBar chartingToolBar = new ChartingToolBar(this);
	private final DrawingToolBar drawingToolBar = new DrawingToolBar(this);
	private final Crosshair crosshair;
	private final ChartingContextMenu chartingContextMenu;
	private final ChartingConfigurationPane configurationPane;
	private final Zoom zoom = new Zoom(this);
	private final ZoomEventListener zoomEventListener = new ZoomEventListener(this);

	public ChartingView() {
		super("Charting Y");

		final BorderPane contentPane = new BorderPane();
		UI.style(contentPane, CSS.chartingView);
		contentPane.setTop(chartingToolBar);
		contentPane.setLeft(drawingToolBar);
		splitPane.setOrientation(Orientation.VERTICAL);

		splitPane.getItems().add(primaryChart);
		UI.style(splitPane, CSS.undecorated, CSS.glass);
		contentPane.setCenter(splitPane);

		backgroundProperty().bind(primaryChart.backgroundProperty());
		rangeSlider.backgroundProperty().bind(backgroundProperty());
		contentPane.setBottom(rangeSlider);
		setCenter(contentPane);

		crosshair = new Crosshair(this);
		contentPane.getChildren().add(crosshair);
		chartingContextMenu = new ChartingContextMenu(this);
		configurationPane = new ChartingConfigurationPane(this);

		panMouseEventStrategy.attach(this);

		final Date to = new Date();
		final Date from = spanProperty.get().add(to, -1);
		barCriteria.setFrom(from);
		barCriteria.setTo(to);
		barCriteria.setBarSpan(barSpanProperty.get());

		rangeSlider.lowValueProperty().addListener(new ChangeListener<Number>() {
			@Override
			public void changed(final ObservableValue<? extends Number> observable, final Number oldValue, final Number newValue) {
				if (!rangeSlider.isHighValueChanging()) {
					updateAxisBounds();
				}
			}
		});
		rangeSlider.highValueProperty().addListener(new ChangeListener<Number>() {
			@Override
			public void changed(final ObservableValue<? extends Number> observable, final Number oldValue, final Number newValue) {
				updateAxisBounds();
			}
		});
		spanProperty.addListener(new ChangeListener<Span>() {
			@Override
			public void changed(final ObservableValue<? extends Span> observable, final Span oldValue, final Span newValue) {
				final Calendar calendar = Calendar.getInstance();
				barCriteria.setTo(calendar.getTime());
				newValue.add(calendar, -1);
				barCriteria.setFrom(calendar.getTime());
				primaryChart.getPrimaryPlot().load();
			}
		});
		barSpanProperty.addListener(new ChangeListener<BarSpan>() {
			@Override
			public void changed(final ObservableValue<? extends BarSpan> observable, final BarSpan oldValue, final BarSpan newValue) {
				barCriteria.setBarSpan(newValue);
				primaryChart.getPrimaryPlot().load();
			}
		});
		barTypeProperty.addListener(new ChangeListener<BarType>() {
			@Override
			public void changed(final ObservableValue<? extends BarType> observable, final BarType oldValue, final BarType newValue) {
				primaryChart.getPrimaryPlot().setLoadedBars(primaryChart.getPrimaryPlot().getLoadedBars());
				for (final Plot<Scrip, Bar> plot : primaryChart.getPlots()) {
					plot.renewDataNodes();
				}
				updateAxisBounds();
			}
		});
		chartsProperty.addListener(new ListChangeListener<Chart<?, ?>>() {
			@Override
			public void onChanged(final javafx.collections.ListChangeListener.Change<? extends Chart<?, ?>> change) {
				while (change.next()) {
					if (change.wasAdded()) {
						splitPane.getItems().addAll(change.getAddedSubList());
					}
					if (change.wasRemoved()) {
						splitPane.getItems().removeAll(change.getRemoved());
					}
					final int size = chartsProperty.size();
					for (int index = 0; index < size; index++) {
						splitPane.setDividerPosition(index, 1 - ((size - index) * 0.2));
					}
				}
			}
		});
		addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
			@Override
			public void handle(final KeyEvent event) {
				HasScrip hasScrip = null;
				if (KeyCode.PAGE_UP.equals(event.getCode()) || KeyCode.UP.equals(event.getCode())) {
					hasScrip = getNavigable().previous(getNavigable().current());
				} else if (KeyCode.PAGE_DOWN.equals(event.getCode()) || KeyCode.DOWN.equals(event.getCode())) {
					hasScrip = getNavigable().next(getNavigable().current());
				}
				if ((null != hasScrip) && (null != hasScrip.getScrip())) {
					primaryChart.getPrimaryPlot().setExtraValue(hasScrip.getScrip());
					primaryChart.getPrimaryPlot().load();
					getNavigable().scrollClearAndSelect(hasScrip);
				}
				event.consume();
				requestFocus();
			}
		});
		addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
			@Override
			public void handle(final MouseEvent event) {
				if (MouseButton.SECONDARY.equals(event.getButton())) {
					chartingContextMenu.show(ChartingView.this, event.getScreenX(), event.getScreenY());
				}
			}
		});
		mouseEventStrategyProperty.addListener(new ChangeListener<ChartingMouseEventStrategy>() {
			@Override
			public void changed(final ObservableValue<? extends ChartingMouseEventStrategy> observable, final ChartingMouseEventStrategy oldValue, final ChartingMouseEventStrategy newValue) {
				if (null != oldValue) {
					oldValue.detach(ChartingView.this);
				}
				if (null != newValue) {
					newValue.attach(ChartingView.this);
				} else {
					setMouseEventStrategy(panMouseEventStrategy);
				}
			}
		});
	}

	@Override
	public void configure() {
		final ConfigurationPopupControl configurationPopupControl = new ConfigurationPopupControl(configurationPane);
		configurationPopupControl.setOnShowing(new EventHandler<WindowEvent>() {
			@Override
			public void handle(final WindowEvent event) {
				configurationPane.prepare();
			}
		});
		configurationPopupControl.getApplyButton().setOnAction(new EventHandler<ActionEvent>() {
			@Override
			public void handle(final ActionEvent event) {
				configurationPane.apply();
			}
		});
		configurationPopupControl.show(StoxStage.getInstance());
	}

	public Crosshair getCrosshair() {
		return crosshair;
	}

	public ChartingToolBar getChartingToolBar() {
		return chartingToolBar;
	}

	public DrawingToolBar getDrawingToolBar() {
		return drawingToolBar;
	}

	public ChartingContextMenu getChartingContextMenu() {
		return chartingContextMenu;
	}

	public ChartingConfigurationPane getConfigurationPane() {
		return configurationPane;
	}

	public ObjectProperty<ChartingMouseEventStrategy> mouseEventStrategyProperty() {
		return mouseEventStrategyProperty;
	}

	public ChartingMouseEventStrategy getMouseEventStrategy() {
		return mouseEventStrategyProperty.get();
	}

	public void setMouseEventStrategy(final ChartingMouseEventStrategy value) {
		mouseEventStrategyProperty.set(value);
	}

	public ListProperty<Chart<?, ?>> chartsProperty() {
		return chartsProperty;
	}

	public List<Chart<?, ?>> getCharts() {
		return chartsProperty;
	}

	public ObjectProperty<Navigable<HasScrip>> navigableProperty() {
		return navigableProperty;
	}

	public Navigable<HasScrip> getNavigable() {
		return navigableProperty.get();
	}

	public void setNavigable(final Navigable<HasScrip> navigable) {
		navigableProperty.set(navigable);
	}

	public ObjectProperty<Span> spanProperty() {
		return spanProperty;
	}

	public Span getSpan() {
		return spanProperty.get();
	}

	public void setSpan(final Span span) {
		spanProperty.set(span);
	}

	public ToggleGroup getMouseEventStrategyToggleGroup() {
		return mouseEventStrategyToggleGroup;
	}

	public ObjectProperty<BarSpan> barSpanProperty() {
		return barSpanProperty;
	}

	public BarSpan getBarSpan() {
		return barSpanProperty.get();
	}

	public void setBarSpan(final BarSpan barSpan) {
		barSpanProperty.set(barSpan);
	}

	public ObjectProperty<BarType> barTypeProperty() {
		return barTypeProperty;
	}

	public BarType getBarType() {
		return barTypeProperty.get();
	}

	public void setBarType(final BarType barType) {
		barTypeProperty.set(barType);
	}

	public DoubleProperty barGapProperty() {
		return barGapProperty;
	}

	public double getBarGap() {
		return barGapProperty.get();
	}

	public void setBarGap(final double gap) {
		barGapProperty.set(gap);
	}

	/**
	 * Utility methods
	 *
	 * @return
	 */

	public BarCriteria currentBarCriteria() {
		return barCriteria;
	}


	public void updateAxisBounds() {
		primaryChart.updateAxisBounds();
		for (final Chart<?, ?> chart : chartsProperty) {
			chart.updateAxisBounds();
		}
	}

	public RangeSlider getRangeSlider() {
		return rangeSlider;
	}

	/**
	 * Getter/Setters
	 *
	 */
	public PrimaryChart getPrimaryChart() {
		return primaryChart;
	}

	public Plot<Scrip, Bar> getVolumePlot() {
		return volumePlot;
	}

	public SplitPane getSplitPane() {
		return splitPane;
	}

	public boolean isMaintainRatio() {
		return maintainRatio;
	}

	public Zoom getZoom() {
		return zoom;
	}

	public double getPnfBoxSize() {
		return pnfBoxSize;
	}

	public void setPnfBoxSize(final double boxSize) {
		pnfBoxSize = boxSize;
	}

	public boolean isPnfBoxSizeInPercentage() {
		return pnfBoxSizeInPercentage;
	}

	public void setPnfBoxSizeInPercentage(final boolean boxSizeInPercentage) {
		pnfBoxSizeInPercentage = boxSizeInPercentage;
	}

	public int getPnfReversalAmount() {
		return pnfReversalAmount;
	}

	public void setPnfReversalAmount(final int reversalAmount) {
		pnfReversalAmount = reversalAmount;
	}

	public double getKagiReversalSize() {
		return kagiReversalSize;
	}

	public int getLineBreakReversalAmount() {
		return lineBreakReversalAmount;
	}

	public void setLineBreakReversalAmount(final int lineBreakReversalAmount) {
		this.lineBreakReversalAmount = lineBreakReversalAmount;
	}

	public void setKagiReversalSize(final double kagiReversalSize) {
		this.kagiReversalSize = kagiReversalSize;
	}

	public boolean isKagiReversalSizeInPercentage() {
		return kagiReversalSizeInPercentage;
	}

	public void setKagiReversalSizeInPercentage(final boolean kagiReversalSizeInPercentage) {
		this.kagiReversalSizeInPercentage = kagiReversalSizeInPercentage;
	}

	public boolean isRenkoReversalSizeInPercentage() {
		return renkoReversalSizeInPercentage;
	}

	public void setRenkoReversalSize(final double renkoReversalSize) {
		this.renkoReversalSize = renkoReversalSize;
	}

	public void setRenkoReversalSizeInPercentage(final boolean renkoReversalSizeInPercentage) {
		this.renkoReversalSizeInPercentage = renkoReversalSizeInPercentage;
	}

	public double getRenkoReversalSize() {
		return renkoReversalSize;
	}

	public void setMaintainRatio(final boolean maintainRatio) {
		this.maintainRatio = maintainRatio;
		updateAxisBounds();
	}

	public double getZoomStep() {
		return zoomEventListener.getStep();
	}

	public void setZoomStep(final double step) {
		zoomEventListener.setStep(step);
	}

	public Chart<?, ?> resolveChart(final double sceneX, final double sceneY) {
		Chart<?, ?> chart = null;
		Point2D point = primaryChart.sceneToLocal(sceneX, sceneY);
		if (primaryChart.contains(point)) {
			chart = primaryChart;
		} else {
			for (final Chart<?, ?> secondaryChart : getCharts()) {
				point = secondaryChart.sceneToLocal(sceneX, sceneY);
				if (secondaryChart.contains(point)) {
					chart = secondaryChart;
					break;
				}
			}
		}
		return chart;
	}

	public Node prepareMouseEventStrategyToggleButton(final ToggleButton button, final ChartingMouseEventStrategy strategy) {
		button.setToggleGroup(mouseEventStrategyToggleGroup);
		strategy.setCompletionCallback(new Callback<Void, Void>() {
			@Override
			public Void execute(final Void payload) {
				button.setSelected(false);
				return null;
			}
		});
		button.selectedProperty().addListener(new ChangeListener<Boolean>() {
			@Override
			public void changed(final ObservableValue<? extends Boolean> observable, final Boolean oldValue, final Boolean newValue) {
				if (newValue) {
					mouseEventStrategyProperty.set(strategy);
				} else if ((null != mouseEventStrategyProperty.get()) && strategy.equals(mouseEventStrategyProperty.get())) {
					mouseEventStrategyProperty.set(null);
				}
			}
		});
		return button;
	}

}
